package com.stark.wechat.service.menu;

import com.dtflys.forest.Forest;
import com.fasterxml.jackson.databind.JsonNode;
import com.stark.wechat.core.Config;
import com.stark.wechat.service.menu.model.AddConditionalMenuRequestPayload;
import com.stark.wechat.service.menu.model.Button;
import com.stark.wechat.service.menu.model.GetCurrentSelfmenuInfoResponsePayload;
import com.stark.wechat.service.menu.model.GetMenuResponsePayload;
import com.stark.wechat.util.JacksonUtils;
import com.stark.wechat.util.ResponseUtils;
import lombok.Builder;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static java.util.Objects.requireNonNull;

/**
 * 自定义菜单接口
 *
 * @author <a href="mengbin@eastsoft.com.cn">Ben</a>
 * @version 1.0.0
 * @see <a href="https://developers.weixin.qq.com/doc/service/guide/product/menu/intro.html">能力接入 / 自定义菜单</a>
 * @since 2025/8/6
 */
@Builder
public class CustomMenuService {

    private final Config config;

    /**
     * 创建自定义菜单
     * <p>该接口用于创建公众号/服务号的自定义菜单。
     *
     * <p><b>注意事项</b>
     * <ol>
     *     <li>自定义菜单最多包括3个一级菜单，每个一级菜单最多包含5个二级菜单。</li>
     *     <li>一级菜单最多4个汉字，二级菜单最多8个汉字，多出来的部分将会以“...”代替。</li>
     *     <li>创建自定义菜单后，菜单的刷新策略是，在用户进入公众号/服务号会话页或公众号/服务号profile页时，如果发现上一次拉取菜单的请求在5分钟以前，就会拉取一下菜单，如果菜单有更新，就会刷新客户端的菜单。测试时可以尝试取消关注公众号/服务号后再次关注，则可以看到创建后的效果。​</li>
     * </ol>
     *
     * @param accessToken 接口调用凭证，可使用 <a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AccessToken.html">access_token</a>、<a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AuthorizerAccessToken.html">authorizer_access_token</a>
     * @param buttonList  一级菜单结构体数组
     * @see <a href="https://developers.weixin.qq.com/doc/service/api/custommenu/api_createcustommenu.html">自定义菜单 / 创建自定义菜单</a>
     */
    public void createCustomMenu(String accessToken, List<Button> buttonList) {
        requireNonNull(accessToken);
        requireNonNull(buttonList);

        String responseStr = Forest
                .post(config.getWechatGateway() + "/cgi-bin/menu/create")
                .contentTypeJson()
                .addQuery("access_token", accessToken)
                .addBody("button", buttonList)
                .connectTimeout(config.getConnectTimeout())
                .readTimeout(config.getReadTimeout())
                .executeAsString();
        JsonNode root = JacksonUtils.readTree(responseStr);
        ResponseUtils.findError("创建自定义菜单", root, CODE_MAP.get("createCustomMenu"));
    }

    /**
     * 查询自定义菜单信息
     * <p>本接口提供公众号当前使用的自定义菜单的配置，如果公众号是通过API调用设置的菜单，则返回菜单的开发配置，而如果公众号是在公众平台官网通过网站功能发布菜单，则本接口返回运营者设置的菜单配置。
     *
     * @param accessToken 接口调用凭证，可使用 <a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AccessToken.html">access_token</a>、<a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AuthorizerAccessToken.html">authorizer_access_token</a>
     * @see <a href="https://developers.weixin.qq.com/doc/service/api/custommenu/api_getcurrentselfmenuinfo.html">自定义菜单 / 查询自定义菜单信息</a>
     */
    public GetCurrentSelfmenuInfoResponsePayload getCurrentSelfmenuInfo(String accessToken) {
        requireNonNull(accessToken);

        String responseStr = Forest
                .get(config.getWechatGateway() + "/cgi-bin/get_current_selfmenu_info")
                .addQuery("access_token", accessToken)
                .contentTypeJson()
                .connectTimeout(config.getConnectTimeout())
                .readTimeout(config.getReadTimeout())
                .executeAsString();
        JsonNode root = JacksonUtils.readTree(responseStr);
        ResponseUtils.findError("查询自定义菜单信息", root, CODE_MAP.get("getCurrentSelfmenuInfo"));
        return JacksonUtils.deserialize(responseStr, GetCurrentSelfmenuInfoResponsePayload.class);
    }

    /**
     * 获取自定义菜单配置
     * <p>使用接口创建自定义菜单后，开发者还可使用接口查询自定义菜单的结构。
     *
     * @param accessToken 接口调用凭证，可使用 <a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AccessToken.html">access_token</a>、<a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AuthorizerAccessToken.html">authorizer_access_token</a>
     * @see <a href="https://developers.weixin.qq.com/doc/service/api/custommenu/api_getmenu.html">自定义菜单 / 获取自定义菜单配置</a>
     */
    public GetMenuResponsePayload getMenu(String accessToken) {
        requireNonNull(accessToken);

        String responseStr = Forest
                .get(config.getWechatGateway() + "/cgi-bin/menu/get")
                .addQuery("access_token", accessToken)
                .contentTypeJson()
                .connectTimeout(config.getConnectTimeout())
                .readTimeout(config.getReadTimeout())
                .executeAsString();
        JsonNode root = JacksonUtils.readTree(responseStr);
        ResponseUtils.findError("获取自定义菜单配置", root, CODE_MAP.get("getMenu"));
        return JacksonUtils.deserialize(responseStr, GetMenuResponsePayload.class);
    }

    /**
     * 删除自定义菜单
     * <p>删除当前使用的自定义菜单。注意：调用此接口会删除默认菜单及全部个性化菜单。
     *
     * @param accessToken 接口调用凭证，可使用 <a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AccessToken.html">access_token</a>、<a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AuthorizerAccessToken.html">authorizer_access_token</a>
     * @see <a href="https://developers.weixin.qq.com/doc/service/api/custommenu/api_deletemenu.html">自定义菜单 / 删除自定义菜单</a>
     */
    public void deleteMenu(String accessToken) {
        requireNonNull(accessToken);

        String responseStr = Forest
                .get(config.getWechatGateway() + "/cgi-bin/menu/delete")
                .addQuery("access_token", accessToken)
                .contentTypeJson()
                .connectTimeout(config.getConnectTimeout())
                .readTimeout(config.getReadTimeout())
                .executeAsString();
        JsonNode root = JacksonUtils.readTree(responseStr);
        ResponseUtils.findError("删除自定义菜单", root, CODE_MAP.get("deleteMenu"));
    }

    /**
     * 创建个性化菜单
     * <p>该接口用于创建公众号/服务号的自定义菜单。
     *
     * @param accessToken    接口调用凭证，可使用 <a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AccessToken.html">access_token</a>、<a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AuthorizerAccessToken.html">authorizer_access_token</a>
     * @param requestPayload 请求体
     * @return 菜单ID
     * @see <a href="https://developers.weixin.qq.com/doc/service/api/custommenu/api_addconditionalmenu.html">自定义菜单 / 创建个性化菜单</a>
     */
    public String addConditionalMenu(String accessToken, AddConditionalMenuRequestPayload requestPayload) {
        requireNonNull(accessToken);
        requireNonNull(requestPayload);

        String body = JacksonUtils.serialize(requestPayload);
        String responseStr = Forest
                .post(config.getWechatGateway() + "/cgi-bin/menu/addconditional")
                .contentTypeJson()
                .addQuery("access_token", accessToken)
                .addBody(body)
                .connectTimeout(config.getConnectTimeout())
                .readTimeout(config.getReadTimeout())
                .executeAsString();
        JsonNode root = JacksonUtils.readTree(responseStr);
        ResponseUtils.findError("创建个性化菜单", root, CODE_MAP.get("addConditionalMenu"));
        return root.get("menuid").asText();
    }

    /**
     * 删除个性化菜单
     * <p>删除指定个性化菜单
     *
     * @param accessToken 接口调用凭证，可使用 <a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AccessToken.html">access_token</a>、<a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AuthorizerAccessToken.html">authorizer_access_token
     * @param menuid      菜单ID
     * @see <a href="https://developers.weixin.qq.com/doc/service/api/custommenu/api_deleteconditionalmenu.html">自定义菜单 / 删除个性化菜单</a>
     */
    public void deleteConditionalMenu(String accessToken, String menuid) {
        requireNonNull(accessToken);
        requireNonNull(menuid);

        String responseStr = Forest
                .post(config.getWechatGateway() + "/cgi-bin/menu/delconditional")
                .addQuery("access_token", accessToken)
                .addBody("menuid", menuid)
                .contentTypeJson()
                .connectTimeout(config.getConnectTimeout())
                .readTimeout(config.getReadTimeout())
                .executeAsString();
        JsonNode root = JacksonUtils.readTree(responseStr);
        ResponseUtils.findError("删除个性化菜单", root, CODE_MAP.get("deleteConditionalMenu"));
    }

    /**
     * 测试个性化菜单匹配结果
     * <p>测试用户看到的菜单配置
     *
     * @param accessToken 接口调用凭证，可使用 <a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AccessToken.html">access_token</a>、<a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AuthorizerAccessToken.html">authorizer_access_token
     * @param userId      用户OpenID或微信号
     * @see <a href="https://developers.weixin.qq.com/doc/service/api/custommenu/api_trymatchmenu.html">自定义菜单 / 测试个性化菜单匹配结果</a>
     */
    public List<Button> tryMatchMenu(String accessToken, String userId) {
        requireNonNull(accessToken);
        requireNonNull(userId);

        String responseStr = Forest
                .post(config.getWechatGateway() + "/cgi-bin/menu/trymatch")
                .addQuery("access_token", accessToken)
                .addBody("user_id", userId)
                .contentTypeJson()
                .connectTimeout(config.getConnectTimeout())
                .readTimeout(config.getReadTimeout())
                .executeAsString();
        JsonNode root = JacksonUtils.readTree(responseStr);
        ResponseUtils.findError("测试个性化菜单匹配结果", root, CODE_MAP.get("tryMatchMenu"));
        return JacksonUtils.deserializeList(root.get("button").toPrettyString(), Button.class);
    }

    private static final Map<String, Map<Long, String>> CODE_MAP;

    static {
        CODE_MAP = new HashMap<>();

        Map<Long, String> codeMap = new HashMap<>();
        codeMap.put(40018L, "不合法的按钮名字长度");
        CODE_MAP.put("createCustomMenu", Collections.unmodifiableMap(codeMap));

        codeMap = new HashMap<>();
        codeMap.put(40001L, "获取 access_token 时 AppSecret 错误，或者 access_token 无效");
        CODE_MAP.put("deleteMenu", Collections.unmodifiableMap(codeMap));

        codeMap = new HashMap<>();
        codeMap.put(-1L, "系统繁忙，请稍候再试");
        codeMap.put(40001L, "获取 access_token 时 AppSecret 错误，或者 access_token 无效");
        codeMap.put(40007L, "不合法的媒体文件 id");
        codeMap.put(40016L, "不合法的按钮个数");
        codeMap.put(40017L, "不合法的按钮类型");
        codeMap.put(40018L, "不合法的按钮名字长度");
        codeMap.put(40019L, "不合法的按钮 KEY 长度");
        codeMap.put(40020L, "不合法的按钮 URL 长度");
        codeMap.put(40023L, "不合法的子菜单按钮个数");
        codeMap.put(40024L, "不合法的子菜单按钮类型");
        codeMap.put(40027L, "不合法的子菜单按钮 URL 长度");
        codeMap.put(40033L, "不合法的请求字符，不能包含 \\uxxxx 格式的字符");
        codeMap.put(40054L, "不合法的子菜单按钮 url 域名");
        codeMap.put(40055L, "不合法的菜单按钮 url 域名");
        codeMap.put(53600L, "图文ID无效");
        codeMap.put(65320L, "匹配规则包含隐私字段");
        CODE_MAP.put("addConditionalMenu", Collections.unmodifiableMap(codeMap));
    }

}
